package com.edward.puff.config;

import com.edward.puff.annotation.EnablePuff;
import com.edward.puff.annotation.Puff;
import com.edward.puff.annotation.TxLogId;
import com.edward.puff.aop.PuffAspect;
import com.edward.puff.aop.PuffRepositoryAspect;
import com.edward.puff.asyn.TransLogRetryTask;
import com.edward.puff.asyn.impl.TransLogDefaultRetryTask;
import com.edward.puff.execute.PuffExecutor;
import com.edward.puff.repository.TransLogRepository;
import com.edward.puff.repository.impl.EmptyRepository;
import com.edward.puff.repository.impl.TransLogDefaultRepository;
import com.edward.puff.utils.ScanUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * @Author: Edward
 * @Date: 2019/11/28 下午12:10
 *
 * <p>
 * puff注册器,启动应用时,创建PuffConfiguration，PuffAspect，注册puff元数据
 */
public class PuffScannerRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        //获取注解属性
        Map<String, Object> annotationAttrs = annotationMetadata.getAnnotationAttributes(EnablePuff.class.getName());
        String[] basePackages = (String[]) annotationAttrs.get("basePackages");
        Class<? extends TransLogRepository> globalRepoClass = (Class<? extends TransLogRepository>) annotationAttrs.get("globalRepository");
        Class<? extends TransLogRetryTask> globalRetryTaskClass = (Class<? extends TransLogRetryTask>) annotationAttrs.get("globalRetryTask");
        boolean enableTask = (Boolean) annotationAttrs.get("enableTask");
        //扫描puff
        List<Method> puffMethod = ScanUtil.scanMethodsWithAnnotation(basePackages, Puff.class);
        //build puff 元数据
        Map<String, PuffMetadata> metadataCache = new HashMap<>();
        Map<String, String> signatureCache = new HashMap<>();
        puffMethod.forEach(method -> {
            Puff puff = method.getAnnotation(Puff.class);
            //signature:方法全名md5(16)
            String signature = DigestUtils.md5Hex(method.toString());
            int txIdIndex = getTxLogIdIndex(method);
            List<PuffMetadata.ArgsTypeIndex> argsTypeIndexes = getArgsTypeIndexes(method);
            PuffMetadata metadata = buildPuffMetadata(method, puff, signature, txIdIndex, argsTypeIndexes);
            signatureCache.put(method.toGenericString(), signature);
            metadataCache.put(signature, metadata);
        });
        //注册 PuffConfiguration
        registerPuffConfiguration(registry, globalRepoClass, metadataCache, signatureCache);
        //注册 PuffAspect
        BeanDefinitionBuilder aspBuilder = BeanDefinitionBuilder.genericBeanDefinition(PuffAspect.class);
        registry.registerBeanDefinition(PuffAspect.class.getSimpleName(), aspBuilder.getBeanDefinition());
        //注册 PuffExecutor
        BeanDefinitionBuilder exeBuilder = BeanDefinitionBuilder.genericBeanDefinition(PuffExecutor.class);
        registry.registerBeanDefinition(PuffExecutor.class.getSimpleName(), exeBuilder.getBeanDefinition());
        //注册 PuffRepositoryAspect
        BeanDefinitionBuilder repoAspBuilder = BeanDefinitionBuilder.genericBeanDefinition(PuffRepositoryAspect.class);
        registry.registerBeanDefinition(PuffRepositoryAspect.class.getSimpleName(), repoAspBuilder.getBeanDefinition());
        //注册 全局默认实现对象的Bean（如果不应用默认实现，需要使用者主动将实现注入Spring容器）
        registerDefaultImplBean(registry, globalRepoClass, globalRetryTaskClass, enableTask);
    }

    private List<PuffMetadata.ArgsTypeIndex> getArgsTypeIndexes(Method method) {
        Class<?>[] paramTypes = method.getParameterTypes();
        List<PuffMetadata.ArgsTypeIndex> argsTypeIndexes = new ArrayList<>();
        IntStream.range(0, paramTypes.length).forEach(index -> {
            Class<?> paramType = paramTypes[index];
            if (paramType.isPrimitive() || paramType.equals(String.class)) {
                return;
            }
            argsTypeIndexes.add(new PuffMetadata.ArgsTypeIndex(paramType, index));
        });
        return argsTypeIndexes;
    }

    private void registerDefaultImplBean(BeanDefinitionRegistry registry, Class<? extends TransLogRepository> globalRepoClass,
                                         Class<? extends TransLogRetryTask> globalRetryTaskClass, boolean enableTask) {
        if (globalRepoClass.equals(TransLogDefaultRepository.class)) {
            BeanDefinitionBuilder jtrBuilder = BeanDefinitionBuilder.genericBeanDefinition(TransLogDefaultRepository.class);
            registry.registerBeanDefinition(TransLogDefaultRepository.class.getSimpleName(), jtrBuilder.getBeanDefinition());
        }
        if (globalRetryTaskClass.equals(TransLogDefaultRetryTask.class) && enableTask) {
            BeanDefinitionBuilder gtBuilder = BeanDefinitionBuilder.genericBeanDefinition(globalRetryTaskClass);
            gtBuilder.addPropertyValue("globalRepoClass", globalRepoClass);
            registry.registerBeanDefinition(TransLogDefaultRetryTask.class.getSimpleName(), gtBuilder.getBeanDefinition());
        }
    }

    private void registerPuffConfiguration(BeanDefinitionRegistry registry, Class<? extends TransLogRepository> globalRepoClass,
                                           Map<String, PuffMetadata> metadataCache, Map<String, String> signatureCache) {
        BeanDefinitionBuilder confBuilder = BeanDefinitionBuilder.genericBeanDefinition(PuffConfiguration.class);
        confBuilder.addPropertyValue("signatureCache", signatureCache);
        confBuilder.addPropertyValue("metadataCache", metadataCache);
        confBuilder.addPropertyValue("globalRepoClass", globalRepoClass);
        registry.registerBeanDefinition(PuffConfiguration.class.getSimpleName(), confBuilder.getBeanDefinition());
    }

    /**
     * 获取方法参数中,作为TxLogId的index
     */
    private int getTxLogIdIndex(Method method) {
        int txIdIndex = 0;
        for (Parameter parameter : method.getParameters()) {
            if (parameter.isAnnotationPresent(TxLogId.class)) {
                break;
            }
            txIdIndex++;
        }
        if (txIdIndex >= method.getParameters().length) {
            throw new RuntimeException("no txLogId annotation of " + method.getName());
        }
        return txIdIndex;
    }

    private PuffMetadata buildPuffMetadata(Method method, Puff puff, String signature, int txIdIndex, List<PuffMetadata.ArgsTypeIndex> argsTypeIndexes) {
        return new PuffMetadata()
                .setClazz(method.getDeclaringClass())
                .setMethod(method)
                .setSignature(signature)
                .setTxIdIndex(txIdIndex)
                .setRepositoryClass(puff.curRepository() == EmptyRepository.class ? null : puff.curRepository())
                .setDateSourceName(puff.dateSourceName())
                .setExecutorBeanName(puff.executorBeanName())
                .setArgsTypeIndexes(argsTypeIndexes);
    }

}
